define("dojox/treemap/TreeMap", ["dojo/_base/array", "dojo/_base/lang", "dojo/_base/declare", "dojo/_base/event", "dojo/_base/Color", "dojo/touch",
    "dojo/when", "dojo/on", "dojo/query", "dojo/dom-construct", "dojo/dom-geometry", "dojo/dom-class", "dojo/dom-style",
    "./_utils", "dijit/_WidgetBase", "dojox/widget/_Invalidating", "dojox/widget/Selection",
    "dojo/_base/sniff", "dojo/uacss"],
    function (arr, lang, declare, event, Color, touch, when, on, query, domConstruct, domGeom, domClass, domStyle, utils, _WidgetBase, _Invalidating, Selection, has) {

        return declare("dojox.treemap.TreeMap", [_WidgetBase, _Invalidating, Selection], {
            // summary:
            //		A treemap widget.

            baseClass:"dojoxTreeMap",

            // store: dojo/store/api/Store
            //		The store that contains the items to display.
            store:null,

            // query: Object
            //		A query that can be passed to when querying the store.
            query:{},

            // itemToRenderer: [protected] Object
            //		The associated array item to renderer list.
            itemToRenderer:null,

            // Data
            _dataChanged:false,

            // rootItem: Object
            //		The root item of the treemap, that is the first visible item.
            //		If null the entire treemap hierarchy is shown.
            //		Default is null.
            rootItem:null,
            _rootItemChanged:false,

            // tooltipAttr: String
            //		The attribute of the store item that contains the tooltip text of a treemap cell.
            //		Default is "".
            tooltipAttr:"",

            // areaAttr: String
            //		The attribute of the store item that contains the data used to compute the area of a treemap cell.
            //		Default is "".
            areaAttr:"",
            _areaChanged:false,

            // labelAttr: String
            //		The attribute of the store item that contains the label of a treemap cell.
            //		Default is "label".
            labelAttr:"label",

            // labelThreshold: Number
            //		The starting depth level at which the labels are not displayed anymore on cells.
            //		If NaN no threshold is applied. The depth is the visual depth of the items on the screen not
            //		in the data (i.e. after drill down the depth of an item might change).
            //		Default is NaN.
            labelThreshold:NaN,

            // colorAttr: String
            //		The attribute of the store item that contains the data used to compute the color of a treemap cell.
            //		Default is "".
            colorAttr:"",
            // colorModel: dojox/color/api/ColorModel
            //		The optional color model that converts data to color.
            //		Default is null.
            colorModel:null,
            _coloringChanged:false,

            // groupAttrs: Array
            //		An array of data attributes used to group data in the treemap.
            //		Default is [].
            groupAttrs:[],

            // groupFuncs: Array
            //		An array of grouping functions used to group data in the treemap.
            //		When null, groupAttrs is to compute grouping functions.
            //		Default is null.
            groupFuncs:null,

            _groupFuncs:null,
            _groupingChanged:false,

            constructor:function () {
                this.itemToRenderer = {};
                this.invalidatingProperties = [ "colorModel", "groupAttrs", "groupFuncs", "areaAttr", "areaFunc",
                    "labelAttr", "labelFunc", "labelThreshold", "tooltipAttr", "tooltipFunc",
                    "colorAttr", "colorFunc", "rootItem" ];
            },

            getIdentity:function (item) {
                return item.__treeID ? item.__treeID : this.store.getIdentity(item);
            },

            resize:function (box) {
                if (box) {
                    domGeom.setMarginBox(this.domNode, box);
                    this.invalidateRendering();
                }
            },

            postCreate:function () {
                this.inherited(arguments);
                this.connect(this.domNode, "mouseover", this._onMouseOver);
                this.connect(this.domNode, "mouseout", this._onMouseOut);
                this.connect(this.domNode, touch.release, this._onMouseUp);
                this.domNode.setAttribute("role", "presentation");
                this.domNode.setAttribute("aria-label", "treemap");
            },

            buildRendering:function () {
                this.inherited(arguments);
                this.refreshRendering();
            },

            refreshRendering:function () {
                var forceCreate = false;

                if (this._dataChanged) {
                    this._dataChanged = false;
                    this._groupingChanged = true;
                    this._coloringChanged = true;
                }

                if (this._groupingChanged) {
                    this._groupingChanged = false;
                    this._set("rootItem", null);
                    this._updateTreeMapHierarchy();
                    forceCreate = true;
                }

                if (this._rootItemChanged) {
                    this._rootItemChanged = false;
                    forceCreate = true;
                }

                if (this._coloringChanged) {
                    this._coloringChanged = false;
                    if (this.colorModel != null && this._data != null && this.colorModel.initialize) {
                        this.colorModel.initialize(this._data, lang.hitch(this, function (item) {
                            return this.colorFunc(item, this.store);
                        }));
                    }
                }

                if (this._areaChanged) {
                    this._areaChanged = false;
                    this._removeAreaForGroup();
                }

                if (this.domNode == undefined || this._items == null) {
                    return;
                }

                if (forceCreate) {
                    domConstruct.empty(this.domNode);
                }

                var rootItem = this.rootItem;

                if (rootItem != null) {
                    if (this._isLeaf(rootItem)) {
                        rootItem = this._getRenderer(rootItem).parentItem;
                    }
                }

                var box = domGeom.getMarginBox(this.domNode);
                if (rootItem != null) {
                    this._buildRenderer(this.domNode, null, rootItem, {
                        x:box.l, y:box.t, w:box.w, h:box.h
                    }, 0, forceCreate);
                } else {
                    this._buildChildrenRenderers(this.domNode, rootItem ? rootItem : { __treeRoot:true, children:this._items },
                        0, forceCreate, box);
                }
            },

            _setRootItemAttr:function (value) {
                this._rootItemChanged = true;
                this._set("rootItem", value);
            },

            _setStoreAttr:function (value) {
                var r;
                if (value != null) {
                    var results = value.query(this.query);
                    if (results.observe) {
                        // user asked us to observe the store
                        results.observe(lang.hitch(this, this._updateItem), true);
                    }
                    r = when(results, lang.hitch(this, this._initItems));
                } else {
                    r = this._initItems([]);
                }
                this._set("store", value);
                return r;
            },

            _initItems:function (items) {
                this._dataChanged = true;
                this._data = items;
                this.invalidateRendering();
                return items;
            },

            _updateItem:function (item, previousIndex, newIndex) {
                if (previousIndex != -1) {
                    if (newIndex != previousIndex) {
                        // this is a remove or a move
                        this._data.splice(previousIndex, 1);
                    } else {
                        // this is a put, previous and new index identical
                        // we don't know what has change exactly with store API
                        this._data[newIndex] = item;
                    }
                } else if (newIndex != -1) {
                    // this is a add
                    this._data.splice(newIndex, 0, item);
                }
                // as we have no details let's refresh everything...
                this._dataChanged = true;
                this.invalidateRendering();
            },

            _setGroupAttrsAttr:function (value) {
                this._groupingChanged = true;
                if (this.groupFuncs == null) {
                    if (value != null) {
                        this._groupFuncs = arr.map(value, function (attr) {
                            return function (item) {
                                return item[attr];
                            };
                        });
                    } else {
                        this._groupFuncs = null;
                    }
                }
                this._set("groupAttrs", value);
            },

            _setGroupFuncsAttr:function (value) {
                this._groupingChanged = true;
                this._set("groupFuncs", this._groupFuncs = value);
                if (value == null && this.groupAttrs != null) {
                    this._groupFuncs = arr.map(this.groupAttrs, function (attr) {
                        return function (item) {
                            return item[attr];
                        };
                    });
                }
            },

            _setAreaAttrAttr:function (value) {
                this._areaChanged = true;
                this._set("areaAttr", value);
            },

            // areaFunc: Function
            //		A function that returns the value use to compute the area of cell from a store item.
            //		Default implementation is using areaAttr.
            areaFunc:function (/*Object*/ item, /*dojo/store/api/Store*/ store) {
                return (this.areaAttr && this.areaAttr.length > 0) ? parseFloat(item[this.areaAttr]) : 1;
            },

            _setAreaFuncAttr:function (value) {
                this._areaChanged = true;
                this._set("areaFunc", value);
            },

            // labelFunc: Function
            //		A function that returns the label of cell from a store item.
            //		Default implementation is using labelAttr.
            labelFunc:function (/*Object*/ item, /*dojo/store/api/Store*/ store) {
                var label = (this.labelAttr && this.labelAttr.length > 0) ? item[this.labelAttr] : null;
                return label ? label.toString() : null;
            },

            // tooltipFunc: Function
            //		A function that returns the tooltip of cell from a store item.
            //		Default implementation is using tooltipAttr.
            tooltipFunc:function (/*Object*/ item, /*dojo/store/api/Store*/ store) {
                var tooltip = (this.tooltipAttr && this.tooltipAttr.length > 0) ? item[this.tooltipAttr] : null;
                return tooltip ? tooltip.toString() : null;
            },

            _setColorModelAttr:function (value) {
                this._coloringChanged = true;
                this._set("colorModel", value);
            },

            _setColorAttrAttr:function (value) {
                this._coloringChanged = true;
                this._set("colorAttr", value);
            },

            // colorFunc: Function
            //		A function that returns from a store item the color value of cell or the value used by the
            //		ColorModel to compute the cell color. If a color must be returned it must be in form accepted by the
            //		dojo/_base/Color constructor. If a value must be returned it must be a Number.
            //		Default implementation is using colorAttr.
            colorFunc:function (/*Object*/ item, /*dojo/store/api/Store*/ store) {
                var color = (this.colorAttr && this.colorAttr.length > 0) ? item[this.colorAttr] : 0;
                if (color == null) {
                    color = 0;
                }
                return parseFloat(color);
            },

            _setColorFuncAttr:function (value) {
                this._coloringChanged = true;
                this._set("colorFunc", value);
            },

            createRenderer:function (item, level, kind) {
                // summary:
                //		Creates an item renderer of the specified kind. This is called only when the treemap
                //		is created. Default implementation always create div nodes. It also sets overflow
                //		to hidden and position to absolute on non-header renderers.
                // item: Object
                //		The data item.
                // level: Number
                //		The item depth level.
                // kind: String
                //		The specified kind. This can either be "leaf", "group", "header" or "content".
                // returns: DomNode
                //		The renderer use for the specified kind.
                // tags:
                //		protected
                var div = domConstruct.create("div");
                if (kind != "header") {
                    domStyle.set(div, "overflow", "hidden");
                    domStyle.set(div, "position", "absolute");
                }
                return div;
            },

            styleRenderer:function (renderer, item, level, kind) {
                // summary:
                //		Style the item renderer. This is called each time the treemap is refreshed.
                //		For leaf items it colors them with the color computed from the color model.
                //		For other items it does nothing.
                // renderer: DomNode
                //		The item renderer.
                // item: Object
                //		The data item.
                // level: Number
                //		The item depth level.
                // kind: String
                //		The specified kind. This can either be "leaf", "group", "header" or "content".
                // tags:
                //		protected
                switch (kind) {
                    case "leaf":
                        domStyle.set(renderer, "background", this.getColorForItem(item).toHex());
                    case "header":
                        var label = this.getLabelForItem(item);
                        if (label && (isNaN(this.labelThreshold) || level < this.labelThreshold)) {
                            renderer.innerHTML = label;
                        } else {
                            domConstruct.empty(renderer);
                        }
                        break;
                    default:

                }
            },

            _updateTreeMapHierarchy:function () {
                if (this._data == null) {
                    return;
                }
                if (this._groupFuncs != null && this._groupFuncs.length > 0) {
                    this._items = utils.group(this._data, this._groupFuncs, lang.hitch(this, this._getAreaForItem)).children;
                } else {
                    this._items = this._data;
                }
            },

            _removeAreaForGroup:function (item) {
                var children;
                if (item != null) {
                    if (item.__treeValue) {
                        delete item.__treeValue;
                        children = item.children;
                    } else {
                        // not a grouping item
                        return;
                    }
                } else {
                    children = this._items;
                }
                if (children) {
                    for (var i = 0; i < children.length; ++i) {
                        this._removeAreaForGroup(children[i]);
                    }
                }
            },

            _getAreaForItem:function (item) {
                var area = this.areaFunc(item, this.store);
                return isNaN(area) ? 0 : area;
            },

            _computeAreaForItem:function (item) {
                var value;
                if (item.__treeID) { // group
                    value = item.__treeValue;
                    if (!value) {
                        value = 0;
                        var children = item.children;
                        for (var i = 0; i < children.length; ++i) {
                            value += this._computeAreaForItem(children[i]);
                        }
                        item.__treeValue = value;
                    }
                } else {
                    value = this._getAreaForItem(item);
                }
                return value;
            },

            getColorForItem:function (item) {
                // summary:
                //		Returns the color for a given item. This either use the colorModel if not null
                //		or just the result of the colorFunc.
                // item: Object
                //		The data item.
                // tags:
                //		protected
                var value = this.colorFunc(item, this.store);
                if (this.colorModel != null) {
                    return this.colorModel.getColor(value);
                } else {
                    return new Color(value);
                }
            },

            getLabelForItem:function (item) {
                // summary:
                //		Returns the label for a given item.
                // item: Object
                //		The data item.
                // tags:
                //		protected
                return item.__treeName ? item.__treeName : this.labelFunc(item, this.store);
            },

            _buildChildrenRenderers:function (domNode, item, level, forceCreate, delta, anim) {
                var children = item.children;
                var box = domGeom.getMarginBox(domNode);

                var solution = utils.solve(children, box.w, box.h, lang.hitch(this,
                    this._computeAreaForItem), !this.isLeftToRight());

                var rectangles = solution.rectangles;

                if (delta) {
                    rectangles = arr.map(rectangles, function (item) {
                        item.x += delta.l;
                        item.y += delta.t;
                        return item;
                    });
                }

                var rectangle;
                for (var j = 0; j < children.length; ++j) {
                    rectangle = rectangles[j];
                    this._buildRenderer(domNode, item, children[j], rectangle, level, forceCreate, anim);
                }
            },

            _isLeaf:function (item) {
                return !item.children;
            },

            _isRoot:function (item) {
                return item.__treeRoot;
            },

            _getRenderer:function (item, anim, parent) {
                if (anim) {
                    // while animating we do that on a copy of the subtree
                    // so we can use our hash object to get to the renderer
                    for (var i = 0; i < parent.children.length; ++i) {
                        if (parent.children[i].item == item) {
                            return parent.children[i];
                        }
                    }
                }
                return this.itemToRenderer[this.getIdentity(item)];
            },

            _buildRenderer:function (container, parent, child, rect, level, forceCreate, anim) {
                var isLeaf = this._isLeaf(child);
                var renderer = !forceCreate ? this._getRenderer(child, anim, container) : null;
                renderer = isLeaf ? this._updateLeafRenderer(renderer, child, level) : this._updateGroupRenderer(renderer,
                    child, level);
                if (forceCreate) {
                    renderer.level = level;
                    renderer.item = child;
                    renderer.parentItem = parent;
                    this.itemToRenderer[this.getIdentity(child)] = renderer;
                    // update its selection status
                    this.updateRenderers(child);
                }

                // in some cases the computation might be slightly incorrect (0.0000...1)
                // and due to the floor this will cause 1px gaps

                var x = Math.floor(rect.x);
                var y = Math.floor(rect.y);
                var w = Math.floor(rect.x + rect.w + 0.00000000001) - x;
                var h = Math.floor(rect.y + rect.h + 0.00000000001) - y;

                // before sizing put the item inside its parent so that styling
                // is applied and taken into account
                if (forceCreate) {
                    domConstruct.place(renderer, container);
                }

                domGeom.setMarginBox(renderer, {
                    l:x, t:y, w:w, h:h
                });

                if (!isLeaf) {
                    var box = domGeom.getContentBox(renderer);
                    this._layoutGroupContent(renderer, box.w, box.h, level + 1, forceCreate, anim);
                }

                this.onRendererUpdated({ renderer:renderer, item:child, kind:isLeaf ? "leaf" : "group", level:level });
            },

            _layoutGroupContent:function (renderer, width, height, level, forceCreate, anim) {
                var header = query(".dojoxTreeMapHeader", renderer)[0];
                var content = query(".dojoxTreeMapGroupContent", renderer)[0];
                if (header == null || content == null) {
                    return;
                }

                var box = domGeom.getMarginBox(header);

                // If the header is too high, reduce its area
                // and don't show the children..
                if (box.h > height) {
                    // TODO: this might cause pb when coming back to visibility later
                    // as the getMarginBox of the header will keep that value?
                    box.h = height;
                    domStyle.set(content, "display", "none");
                } else {
                    domStyle.set(content, "display", "block");
                    domGeom.setMarginBox(content, {
                        l:0, t:box.h, w:width, h:(height - box.h)
                    });
                    this._buildChildrenRenderers(content, renderer.item, level, forceCreate, null, anim);
                }

                domGeom.setMarginBox(header, {
                    l:0, t:0, w:width, h:box.h
                });
            },

            _updateGroupRenderer:function (renderer, item, level) {
                // summary:
                //		Update a group renderer. This creates the renderer if not already created,
                //		call styleRender for it and recurse into children.
                // renderer: DomNode
                //		The item renderer.
                // item: Object
                //		The data item.
                // level: Number
                //		The item depth level.
                // tags:
                //		private
                var forceCreate = renderer == null;
                if (renderer == null) {
                    renderer = this.createRenderer("div", level, "group");
                    domClass.add(renderer, "dojoxTreeMapGroup");
                }
                this.styleRenderer(renderer, item, level, "group");
                var header = query(".dojoxTreeMapHeader", renderer)[0];
                header = this._updateHeaderRenderer(header, item, level);
                if (forceCreate) {
                    domConstruct.place(header, renderer);
                }
                var content = query(".dojoxTreeMapGroupContent", renderer)[0];
                content = this._updateGroupContentRenderer(content, item, level);
                if (forceCreate) {
                    domConstruct.place(content, renderer);
                }
                return renderer;
            },

            _updateHeaderRenderer:function (renderer, item, level) {
                // summary:
                //		Update a leaf renderer. This creates the renderer if not already created,
                //		call styleRender for it and set the label as its innerHTML.
                // renderer: DomNode
                //		The item renderer.
                // item: Object
                //		The data item.
                // level: Number
                //		The item depth level.
                // tags:
                //		private
                if (renderer == null) {
                    renderer = this.createRenderer(item, level, "header");
                    domClass.add(renderer, "dojoxTreeMapHeader");
                    domClass.add(renderer, "dojoxTreeMapHeader_" + level);
                }
                this.styleRenderer(renderer, item, level, "header");
                return renderer;
            },

            _updateLeafRenderer:function (renderer, item, level) {
                // summary:
                //		Update a leaf renderer. This creates the renderer if not already created,
                //		call styleRender for it and set the label as its innerHTML.
                // renderer: DomNode
                //		The item renderer.
                // item: Object
                //		The data item.
                // level: Number
                //		The item depth level.
                // tags:
                //		private
                if (renderer == null) {
                    renderer = this.createRenderer(item, level, "leaf");
                    domClass.add(renderer, "dojoxTreeMapLeaf");
                    domClass.add(renderer, "dojoxTreeMapLeaf_" + level);
                }
                this.styleRenderer(renderer, item, level, "leaf");
                var tooltip = this.tooltipFunc(item, this.store);
                if (tooltip) {
                    renderer.title = tooltip;
                }
                return renderer;
            },

            _updateGroupContentRenderer:function (renderer, item, level) {
                // summary:
                //		Update a group content renderer. This creates the renderer if not already created,
                //		and call styleRender for it.
                // renderer:
                //		The item renderer.
                // item: Object
                //		The data item.
                // level: Number
                //		The item depth level.
                // tags:
                //		private
                if (renderer == null) {
                    renderer = this.createRenderer(item, level, "content");
                    domClass.add(renderer, "dojoxTreeMapGroupContent");
                    domClass.add(renderer, "dojoxTreeMapGroupContent_" + level);
                }
                this.styleRenderer(renderer, item, level, "content");
                return renderer;
            },

            _getRendererFromTarget:function (target) {
                var renderer = target;
                while (renderer != this.domNode && !renderer.item) {
                    renderer = renderer.parentNode;
                }
                return renderer;
            },

            _onMouseOver:function (e) {
                var renderer = this._getRendererFromTarget(e.target);
                if (renderer.item) {
                    var item = renderer.item;
                    this._hoveredItem = item;
                    this.updateRenderers(item);
                    this.onItemRollOver({renderer:renderer, item:item, triggerEvent:e});
                }
            },

            _onMouseOut:function (e) {
                var renderer = this._getRendererFromTarget(e.target);
                if (renderer.item) {
                    var item = renderer.item;
                    this._hoveredItem = null;
                    this.updateRenderers(item);
                    this.onItemRollOut({renderer:renderer, item:item, triggerEvent:e});
                }
            },

            _onMouseUp:function (e) {
                var renderer = this._getRendererFromTarget(e.target);
                if (renderer.item) {
                    this.selectFromEvent(e, renderer.item, e.currentTarget, true);
                    //event.stop(e);
                }
            },

            onRendererUpdated:function () {
                // summary:
                //		Called when a renderer has been updated. This is called after creation, styling and sizing for
                //		each group and leaf renderers. For group renders this is also called after creation of children
                //		renderers.
                // tags:
                //		callback
            },

            onItemRollOver:function () {
                // summary:
                //		Called when an item renderer has been hovered.
                // tags:
                //		callback
            },

            onItemRollOut:function () {
                // summary:
                //		Called when an item renderer has been rolled out.
                // tags:
                //		callback
            },

            updateRenderers:function (items) {
                // summary:
                //		Updates the renderer(s) that represent the specified item(s).
                // item: Object|Array
                //		The item(s).
                if (!items) {
                    return;
                }
                if (!lang.isArray(items)) {
                    items = [items];
                }
                for (var i = 0; i < items.length; i++) {
                    var item = items[i];
                    var renderer = this._getRenderer(item);
                    // at init time the renderer might not be ready
                    if (!renderer) {
                        continue;
                    }
                    var selected = this.isItemSelected(item);
                    var ie = has("ie");
                    var div;
                    if (selected) {
                        domClass.add(renderer, "dojoxTreeMapSelected");
                        if (ie && (has("quirks") || ie < 9)) {
                            // let's do all of this only if not already done
                            div = renderer.previousSibling;
                            var rStyle = domStyle.get(renderer);
                            if (!div || !domClass.contains(div, "dojoxTreeMapIEHack")) {
                                div = this.createRenderer(item, -10, "group");
                                domClass.add(div, "dojoxTreeMapIEHack");
                                domClass.add(div, "dojoxTreeMapSelected");
                                domStyle.set(div, {
                                    position:"absolute",
                                    overflow:"hidden"
                                });
                                domConstruct.place(div, renderer, "before");
                            }
                            // TODO: might fail if different border widths for different sides
                            var bWidth = 2 * parseInt(domStyle.get(div, "border-width"));
                            if (this._isLeaf(item)) {
                                bWidth -= 1;
                            } else {
                                bWidth += 1;
                            }
                            // if we just drill down some renders might not be laid out?
                            if (rStyle["left"] != "auto") {
                                domStyle.set(div, {
                                    left:(parseInt(rStyle["left"]) + 1) + "px",
                                    top:(parseInt(rStyle["top"]) + 1) + "px",
                                    width:(parseInt(rStyle["width"]) - bWidth) + "px",
                                    height:(parseInt(rStyle["height"]) - bWidth) + "px"
                                });
                            }
                        }
                    } else {
                        if (ie && (has("quirks") || ie < 9)) {
                            div = renderer.previousSibling;
                            if (div && domClass.contains(div, "dojoxTreeMapIEHack")) {
                                div.parentNode.removeChild(div);
                            }
                        }
                        domClass.remove(renderer, "dojoxTreeMapSelected");

                    }
                    if (this._hoveredItem == item) {
                        domClass.add(renderer, "dojoxTreeMapHovered");
                    } else {
                        domClass.remove(renderer, "dojoxTreeMapHovered");
                    }
                    if (selected || this._hoveredItem == item) {
                        domStyle.set(renderer, "zIndex", 20);
                    } else {
                        domStyle.set(renderer, "zIndex", (has("ie") <= 7) ? 0 : "auto");
                    }
                }
            }
        });
    });
